﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Red.Launcher.Utilities;
using P4 = Red.Core.Perforce;

namespace Red.Launcher.ViewModels.Workspaces
{
    public class ImplementedWorkspace : SpecificationWorkspace
    {
        #region Public Properties

        public string ClientName { get; set; }

        public int LatestRevision { get; set; }

        #endregion Public Properties

        #region Implemented Properties

        public override bool IsInstalled => true;
        public override string IsInstalledDescription => "Installed";
        public override string PerforceName => ClientName;
        public override string PerforceNameShort => ( (Database.P4ClientSpecification)WorkspaceInfo ).ClientName;
        public override bool IsUpToDate => Revision == LatestRevision;

        #endregion Implemented Properties

        #region Workspace

        public override Task<bool> CreateInPerforce( string location )
        {
            throw new NotImplementedException();
        }

        public async override Task<bool> UpdateInPerforce()
        {
            // Grab the updated specification from perforce
            SpecificationWrapper specification = await FetchLatestSpecification();

            if ( specification.Specification == null )
            {
                Debug.Print( "Failed to fetch latest specification for '{0}' (id: {1}) from database", WorkspaceInfo.Name, WorkspaceInfo.Id );
                return false;
            }

            // Update perforce with the new specification
            if ( !await UpdatePerforceWorkspace( specification.Specification ) )
            {
                Debug.Print( "Failed to update perforce workspace (couldn't update specification '{0}' (id: {1})", WorkspaceInfo.Name, WorkspaceInfo.Id );
                return false;
            }

            if ( await UpdateDatabase( specification ) != DatabaseHelper.Result.Success )
            {
                Debug.Print( "Failed to update database (couldn't update database with details about changes to specification '{0}' (id: {1})", WorkspaceInfo.Name, WorkspaceInfo.Id );
                return false;
            }

            // Finally, update the workspace object to reflect the new details
            WorkspaceInfo = specification.Specification;
            Revision = specification.Revision;

            OnPropertyChanged( "DisplayName" );

            return true;
        }

        #endregion Workspace

        #region Private Types

        private struct SpecificationWrapper
        {
            public int Revision { get; set; }
            public Database.P4ClientSpecification Specification { get; set; }
        }

        #endregion Private Types

        #region Private Methods

        private async Task<SpecificationWrapper> FetchLatestSpecification()
        {
            string fetchSql = $@"SELECT * FROM [{Settings.DatabaseName}].[dbo].[P4ClientSpecs2] WHERE id = '{WorkspaceInfo.Id}' and revision = 
                (SELECT MAX(revision) revision FROM [{Settings.DatabaseName}].[dbo].[P4ClientSpecs2] WHERE id='{WorkspaceInfo.Id}')";

            Database.P4ClientSpecification specification = null;
            int revision = 0;

            DatabaseHelper.Result error = await DatabaseHelper.FetchAsyncSafe
            (
                fetchSql,
                Settings.DatabaseConnectionString,
                ( Dictionary<string, object> data ) =>
                {
                    specification = (Database.P4ClientSpecification)Database.P4ClientSpecification.METADATA.CreateFromDatabaseRow( data );

                    revision = (int)data[ "revision" ];
                },
                null,
                true
            );

            SpecificationWrapper retVal = new SpecificationWrapper();

            if ( error == DatabaseHelper.Result.Success )
            {
                retVal.Specification = specification;
                retVal.Revision = revision;
            }

            return retVal;
        }

        private string UpdatePerforceStream( P4.Mediator mediator, Database.P4ClientSpecification specification )
        {
            Database.P4ClientSpecification current = (Database.P4ClientSpecification)WorkspaceInfo;

            bool hasStreamFilter = !string.IsNullOrWhiteSpace( specification.StreamFilter );
            bool hadStreamFilter = !string.IsNullOrWhiteSpace( current.StreamFilter );

            // There used to be a filter, but there isn't one any more, so delete the virtual stream and switch to the parent
            if ( hadStreamFilter && !hasStreamFilter )
            {
                // We have to delete the stream later, after we update the workspace, so for now, just return the new stream to use
                return specification.Stream;
            }

            // There was no filter, but now there is, create a new virtual stream
            else if( !hadStreamFilter && hasStreamFilter )
            {
                return CreateStream( mediator, specification );
            }

            // The stream filter has been changed
            else if( hadStreamFilter && hasStreamFilter && current.StreamFilter != specification.StreamFilter )
            {
                //Calling create will update the existing stream
                return CreateStream( mediator, specification );
            }

            // No change to stream filter
            else if( hasStreamFilter )
            {
                P4.StreamBuilder builder = CreateStreamBuilder( specification );
                return builder.FullyQualifiedId;
            }

            // No change to stream filter
            else if ( !hasStreamFilter )
            {
                return specification.Stream;
            }

            // Shouldn't ever reach here
            Debug.Fail( "Not all contingencies were accounted for when updating stream" );
            return null;
        }

        void CleanupPerforceStream( P4.Mediator mediator, Database.P4ClientSpecification specification )
        {
            Database.P4ClientSpecification current = (Database.P4ClientSpecification)WorkspaceInfo;

            bool hasStreamFilter = !string.IsNullOrWhiteSpace( specification.StreamFilter );
            bool hadStreamFilter = !string.IsNullOrWhiteSpace( current.StreamFilter );

            // There used to be a filter, but there isn't one any more, so delete the virtual stream and switch to the parent
            if ( hadStreamFilter && !hasStreamFilter )
            {
                P4.StreamBuilder builder = CreateStreamBuilder( current );
                Debug.Assert( builder.FullyQualifiedId != null );

                mediator.DeleteStream( builder.FullyQualifiedId );
            }
        }

        private async Task<bool> UpdatePerforceWorkspace( Database.P4ClientSpecification specification )
        {
            P4.Mediator mediator = await CreateMediator();

            if ( mediator == null )
                return false;

            // This error handler IDisposable will automatically de-register the error handler, so it won't leak
            using ( MediatorErrorHandler errorHandler = new MediatorErrorHandler( mediator ) )
            {
                string stream = UpdatePerforceStream( mediator, specification );

                if ( stream == null )
                    return false;

                bool workspaceUpdated = mediator.UpdateWorkspace
                (
                    PerforceName,
                    stream,
                    specification.OptionOnSubmit,
                    specification.OptionLineEndings,
                    specification.OptionAllWrite,
                    specification.OptionClobber,
                    specification.OptionCompress,
                    specification.OptionModTime,
                    specification.OptionRMDir
                );

                CleanupPerforceStream( mediator, specification );

                return workspaceUpdated;
            }
        }

        private async Task<DatabaseHelper.Result> UpdateDatabase( SpecificationWrapper specification )
        {
            // Update the database with the new revision
            string updateSql = $@"UPDATE [{Settings.DatabaseName}].[dbo].[ImplementedClientSpecs] SET [revision] = '{specification.Revision}'
                                  WHERE [users_id] = '{User.Id}' AND [p4specs_id] = '{specification.Specification.Id}'";
            return await DatabaseHelper.ExecuteAsyncSafe( updateSql, Settings.DatabaseConnectionString );
        }

        #endregion Private Methods
    }
}
